/**
 * 
 */
package ch.unisi.inf.pfii.teamred.pacman.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import ch.unisi.inf.pfii.teamred.pacman.controller.CreatureListener;

/**
 * This class describes the board of the Pacman game.
 * 
 * @author mark.pruneri@lu.unisi.ch
 * @author luca.vignola@lu.unisi.ch
 */
public final class Board {

	private static final int CHERRY_APPEAR = 125;
	private static final int CHERRY_DISAPPEAR = 175;
	private static final Position CHERRY_POSITION = new Position(18, 13);

	private final Block[][] blocks;
	private final ArrayList<Creature> creatures;
	private final HashMap<Direction, Position> directionToPosition;

	private int counter;

	public Board(final Block[][] blocks, final ArrayList<Creature> creatures) {
		this.blocks = blocks;
		this.creatures = creatures;
		directionToPosition = new HashMap<Direction, Position>();
		counter = 0;

		initializeBoard();
	}

	private final void initializeBoard() {
		fillDirectionToPosition();
		initializeCreatures();
	}

	private final void fillDirectionToPosition() {
		directionToPosition.put(Direction.DOWN, new Position(1, 0));
		directionToPosition.put(Direction.UP, new Position(-1, 0));
		directionToPosition.put(Direction.RIGHT, new Position(0, 1));
		directionToPosition.put(Direction.LEFT, new Position(0, -1));
		directionToPosition.put(Direction.STOP, new Position(0, 0));
	}

	private final void initializeCreatures() {
		fillBlocksWithCreatures();
		setCreatureDirectionToPosition();
		setCreaturesItems();
		setCreaturesOnSameFloor();
		setNeighborsOfAiControlledCreature();
		addListenerToCreatures();
	}

	private final void addListenerToCreatures() {
		for (final Creature creature : creatures) {
			if (creature.isControlledByPlayer()) {
				creature.addCreatureListener(new CreatureListener() {

					public final void creatureChangedDirection(
							final Creature creature) {
					}

					public final void creatureChangedPosition(
							final Creature creature) {
					}

					public final void creatureSetAlive(final Creature creature) {
					}

					public final void creatureSetDead(final Creature creature) {
						restart();
					}

					public final void creatureSetInvulnerable(
							final Creature creature) {
						updateAiControlledCreaturesStatus();
					}

					public final void creatureSetVulnerable(
							final Creature creature) {
						updateAiControlledCreaturesStatus();
					}

					public final void creatureTimerIsRunningOut(
							final Creature creature) {
					}

					public final void creatureTimerTimedOut(
							final Creature creature) {
						updateAiControlledCreaturesStatus();
					}

				});
			} else {
				creature.addCreatureListener(new CreatureListener() {

					public final void creatureChangedDirection(
							final Creature creature) {
					}

					public final void creatureChangedPosition(
							final Creature creature) {
					}

					public final void creatureSetAlive(final Creature creature) {
					}

					public final void creatureSetDead(final Creature creature) {
					}

					public final void creatureSetInvulnerable(
							final Creature creature) {
					}

					public final void creatureSetVulnerable(
							final Creature creature) {
					}

					public final void creatureTimerIsRunningOut(
							final Creature creature) {
					}

					public final void creatureTimerTimedOut(
							final Creature creature) {
					}

				});
			}
		}
	}

	private final void setCreatureDirectionToPosition() {
		for (final Creature creature : creatures) {
			creature.setDirectionToPosition(directionToPosition);
		}
	}

	private final void fillBlocksWithCreatures() {
		for (final Creature creature : creatures) {
			Block block = getBlock(creature.getCurrentPosition());
			if (block.isTraversable()) {
				TraversableBlock traversableBlock = (TraversableBlock) block;
				traversableBlock.addCreature(creature);
			}
		}
	}

	public final ArrayList<Creature> getCreatures() {
		return creatures;
	}

	public final Block[][] getBlocks() {
		return blocks;
	}

	private final Block getBlock(final Position position) {
		return blocks[position.getColumn()][position.getRow()];
	}

	private final boolean isBlockValid(final Position position) {
		int row = position.getRow();
		int column = position.getColumn();

		if (row >= 0 && row < getHeight() && column < getWidth() && column >= 0) {
			Block block = getBlock(position);
			if (block.isTraversable()) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	private final boolean isPositionValid(final Position position) {
		int row = position.getRow();
		int column = position.getColumn();

		if (row >= 0 && row < getHeight() && column < getWidth() && column >= 0) {
			return true;
		} else {
			return false;
		}
	}

	public final int getWidth() {
		return blocks.length;
	}

	public final int getHeight() {
		return blocks[0].length;
	}

	private final void updateTraversableBlock(final TraversableBlock oldBlock,
			final TraversableBlock newBlock, final Creature creature) {
		oldBlock.removeCreature(creature);
		newBlock.addCreature(creature);
	}

	private final void setCreaturesOnSameFloor() {
		for (final Creature creature : creatures) {
			ArrayList<Creature> creaturesOnSameFloor = getBlockOfCreature(
					creature).getCreatures();
			creature.setCreaturesOnCurrentFloor(creaturesOnSameFloor);
		}
	}

	private final void setCreaturesItems() {
		for (final Creature creature : creatures) {
			ArrayList<Item> items = getBlockOfCreature(creature).getItems();
			creature.setCurrentItems(items);
		}
	}

	public final boolean collisionHappened() {
		return getBlockOfCreature(getPacman()).collisionHappened();
	}

	private final TraversableBlock getBlockOfCreature(final Creature creature) {
		return (TraversableBlock) getBlock(creature.getCurrentPosition());
	}

	public final void step() {
		updateCreatures();
		setDeadAiControlledCreaturesToStartPosition();
		setCreaturesItems();
		setCreaturesOnSameFloor();
		setNeighborsOfAiControlledCreature();
		updateCounter();
	}

	private final void updateCounter() {
		counter++;
		if (counter == CHERRY_APPEAR) {
			placeCherry();
		}
		if (counter == CHERRY_DISAPPEAR) {
			removeCherry();
		}
	}

	private final void placeCherry() {
		TraversableBlock traversableBlock = (TraversableBlock) getBlock(CHERRY_POSITION);
		traversableBlock.addItem(new Cherry());
	}

	private final void removeCherry() {
		TraversableBlock traversableBlock = (TraversableBlock) getBlock(CHERRY_POSITION);
		traversableBlock.removeItem(new Cherry());
	}

	private final void updateCreatures() {
		if (counter % 2 == 0) {
			updateAllCreatures();
		} else {
			updateInvulnerableCreatures();
		}
	}

	private final void updateAllCreatures() {
		for (final Creature creature : creatures) {
			Position position = creature.getNextPosition();
			if (isBlockValid(position)) {
				TraversableBlock oldBlock = (TraversableBlock) getBlockOfCreature(creature);
				TraversableBlock newBlock = (TraversableBlock) getBlock(position);
				updateTraversableBlock(oldBlock, newBlock, creature);
				creature.setCurrentPosition(position);
			}
			if (collisionHappened()) {
				break;
			}
		}
	}

	private final void updateInvulnerableCreatures() {
		for (final Creature creature : creatures) {
			if (!creature.isVulnerable() || creature.isControlledByPlayer()) {
				Position position = creature.getNextPosition();
				if (isBlockValid(position)) {
					TraversableBlock oldBlock = (TraversableBlock) getBlockOfCreature(creature);
					TraversableBlock newBlock = (TraversableBlock) getBlock(position);
					updateTraversableBlock(oldBlock, newBlock, creature);
					creature.setCurrentPosition(position);
				}
				if (collisionHappened()) {
					break;
				}
			}
		}
	}

	private final void updateAiControlledCreaturesStatus() {
		if (!getPacman().isVulnerable()) {
			for (final Creature creature : creatures) {
				if (!creature.isControlledByPlayer()
						&& !creature.isVulnerable()) {
					creature.setVulnerable();
				}
			}
		} else {
			for (final Creature creature : creatures) {
				if (!creature.isControlledByPlayer() && creature.isVulnerable()) {
					creature.setInvulnerable();
				}
			}
		}
	}

	private final void setDeadAiControlledCreaturesToStartPosition() {
		for (final Creature creature : creatures) {
			if (!creature.isControlledByPlayer() && creature.isDead()) {
				TraversableBlock oldBlock = getBlockOfCreature(creature);
				creature.reset();
				TraversableBlock newBlock = getBlockOfCreature(creature);
				updateTraversableBlock(oldBlock, newBlock, creature);
			}
		}
	}

	public final Pacman getPacman() {
		for (final Creature creature : creatures) {
			if (creature.isControlledByPlayer()) {
				return (Pacman) creature;
			}
		}

		return null;
	}

	public final void restart() {
		setCreaturesToStartPosition();
		updateAiControlledCreaturesStatus();
		setCreaturesItems();
		setCreaturesOnSameFloor();
		setNeighborsOfAiControlledCreature();
	}

	private final void setCreaturesToStartPosition() {
		for (final Creature creature : creatures) {
			TraversableBlock oldBlock = (TraversableBlock) getBlockOfCreature(creature);
			creature.reset();
			TraversableBlock newBlock = (TraversableBlock) getBlockOfCreature(creature);
			updateTraversableBlock(oldBlock, newBlock, creature);
		}
	}

	private final void setNeighborsOfAiControlledCreature() {
		for (final Creature creature : creatures) {
			if (!creature.isControlledByPlayer()) {
				AiControlledCreature aiCreature = (AiControlledCreature) creature;
				Position aiCreaturePosition = aiCreature.getCurrentPosition();
				aiCreature.setupController(
						getNeighborBlocks(aiCreaturePosition), getPacman()
								.getCurrentPosition());
			}
		}
	}

	private final HashMap<Direction, ArrayList<Block>> getNeighborBlocks(
			final Position position) {
		HashMap<Direction, ArrayList<Block>> neighbors = new HashMap<Direction, ArrayList<Block>>();
		Set<Direction> directions = directionToPosition.keySet();

		for (final Direction direction : directions) {
			if (direction != Direction.STOP) {
				Position newPosition = position.clone();
				Position relativePosition = directionToPosition.get(direction)
						.clone();
				newPosition.addToPosition(relativePosition);
				ArrayList<Block> directionNeighbors = new ArrayList<Block>();
				while (isPositionValid(newPosition)) {
					directionNeighbors.add(getBlock(newPosition));
					newPosition.addToPosition(relativePosition);
				}
				neighbors.put(direction, directionNeighbors);
			}
		}

		return neighbors;
	}

	public final boolean noMoreItemsOnBoard() {
		for (int i = 0; i < blocks.length; i++) {
			for (int j = 0; j < blocks[i].length; j++) {
				if (blocks[i][j].containsItem()) {
					return false;
				}
			}
		}

		return true;
	}

	public final String toString() {
		String boardString = "";
		for (int i = 0; i < blocks.length; i++) {
			for (int j = 0; j < blocks[i].length; j++) {
				boardString += blocks[i][j].toString();
			}
			boardString += "\n";
		}

		return boardString;
	}
}